package main

import (
	"flag"
	"fmt"
	"net"
	"os"
	"syscall"
)

var (
	c = flag.Bool("c", false, "client")
	s = flag.Bool("s", false, "server")
)

var (
	times = 10
	port  = "19880"
)

func C() {
	addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:"+port)
	if err != nil {
		panic(err)
	}
	var clt net.Conn
	clt, err = net.DialUDP("udp", nil, addr)
	if err != nil {
		panic(err)
	}
	clt.Write([]byte("SYN"))
	buffer := &[10]byte{}
	n, err := clt.Read(buffer[:])
	if err != nil {
		panic(err)
	}
	fmt.Println("Recv SYN:", string(buffer[:n]))
	for i := 1; i < times; i++ {
		data := fmt.Sprintf("%d:%s", i, "Hello")
		fmt.Println("Send:", data)
		//time.Sleep(time.Second)
		_, err := clt.Write([]byte(data))
		if err != nil {
			fmt.Println("ERROR:", err)
		}
	}
}

func S() {
	// listen
	laddr, err := net.ResolveUDPAddr("udp", ":"+port)
	if err != nil {
		panic(err)
	}
	svr, err := net.ListenUDP("udp", laddr)
	if err != nil {
		panic(err)
	}

	// setsockopt
	raw, err := svr.SyscallConn()
	if err != nil {
		panic(err)
	}
	var err1 error
	err = raw.Control(func(fd uintptr) {
		err1 = os.NewSyscallError("setsockopt", syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1))
	})
	if err != nil {
		panic(err)
	}
	if err1 != nil {
		panic(err)
	}

	for {
		c, err := accept(svr)
		if err != nil {
			panic(err)
		}
		go serve(c)
		fmt.Println("Accept Success", c.RemoteAddr())
	}
}

func accept(svr *net.UDPConn) (*net.UDPConn, error) {
	var buffer [1024]byte
	_, raddr, err := svr.ReadFromUDP(buffer[:])
	if err != nil {
		return nil, err
	}

	// auth
	// if n != "SYN" {
	//	return errors.New("invalid")
	// }

	var err1 error
	d := &net.Dialer{
		Control: func(network, address string, c syscall.RawConn) error {
			return c.Control(func(fd uintptr) {
				err1 = os.NewSyscallError("setsockopt", syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1))
			})
		},
		LocalAddr: svr.LocalAddr(),
	}
	c, err := d.Dial("udp", raddr.String())
	if err1 != nil {
		return nil, err1
	}
	con := c.(*net.UDPConn)
	_, err = con.Write([]byte("SYN"))
	if err != nil {
		panic(err)
	}
	fmt.Println("Accept:", raddr.String())
	return con, err
}

func serve(con *net.UDPConn) {
	var buffer [1024]byte
	for {
		n, err := con.Read(buffer[:])
		if err != nil {
			fmt.Println(err)
			return
		}
		fmt.Println("Recvfrom:", con.RemoteAddr(), ":", string(buffer[:n]))
	}
}

func main() {
	flag.Parse()
	switch {
	case *c:
		C()
		return
	case *s:
		S()
		return
	}
}
